package jhunter.rt.conf;

import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.bind.JAXBException;

import jhunter.common.exception.ConfigException;
import jhunter.conf.actions.vo.Action;
import jhunter.conf.actions.vo.Actions;
import jhunter.conf.app.vo.ModuleFile;
import jhunter.conf.apps.vo.App;
import jhunter.conf.apps.vo.Apps;
import jhunter.conf.entities.vo.Entities;
import jhunter.conf.entities.vo.Entity;
import jhunter.conf.forms.vo.EasyForm;
import jhunter.conf.forms.vo.Forms;
import jhunter.conf.io.ActionsXmlLoader;
import jhunter.conf.io.AppXmlLoader;
import jhunter.conf.io.AppsXmlLoader;
import jhunter.conf.io.EntitiesXmlLoader;
import jhunter.conf.io.FormsXmlLoader;
import jhunter.conf.io.ModuleXmlLoader;
import jhunter.conf.io.ServicesXmlLoader;
import jhunter.conf.module.vo.Module;
import jhunter.conf.services.vo.Service;
import jhunter.conf.services.vo.Services;
import jhunter.rt.conf.ext.ActionExt;
import jhunter.rt.conf.ext.ActionsExt;
import jhunter.rt.conf.ext.AppExt;
import jhunter.rt.conf.ext.AppsExt;
import jhunter.rt.conf.ext.EntitiesExt;
import jhunter.rt.conf.ext.FormsExt;
import jhunter.rt.conf.ext.ModuleExt;
import jhunter.rt.conf.ext.ServicesExt;

import org.apache.commons.io.monitor.FileAlterationListenerAdaptor;
import org.apache.commons.io.monitor.FileAlterationMonitor;
import org.apache.commons.io.monitor.FileAlterationObserver;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;

@Component
public class AppsConfigLoader implements InitializingBean {

	private org.apache.commons.logging.Log log = LogFactory.getLog(AppsConfigLoader.class);

	@Value("${conf}")
	private String appResource = "apps.xml";

	private Node apps;

	@Autowired
	private AppsXmlLoader appsXmlLoader;

	@Autowired
	private AppXmlLoader appXmlLoader;

	@Autowired
	private ModuleXmlLoader moduleXmlLoader;

	@Autowired
	private ActionsXmlLoader actionsXmlLoader;

	@Autowired
	private ServicesXmlLoader servicesXmlLoader;

	@Autowired
	private EntitiesXmlLoader entitiesXmlLoader;

	@Autowired
	private FormsXmlLoader formsXmlLoader;

	@Value("${dev}")
	private boolean devModel = true;

	private FileAlterationMonitor fileMonitor;

	/**
	 * key : File path value:Tree Node
	 */
	private Map<String, Node> fileNodeMap = new ConcurrentHashMap<String, Node>();

	/**
	 * key: uri; value:Tree Node
	 */
	private Map<PathInfo, Node> uriNodeMap = new ConcurrentHashMap<PathInfo, Node>();

	private String getFilePath(String resource) {
		return ClassUtils.getDefaultClassLoader().getResource(resource).getFile();
	}

	protected void prepareReload(Node node) {
		if (node.getObject() instanceof Reloadable) {
			Reloadable u = (Reloadable) node.getObject();
			u.unload();
		}
		List<Node> nodes = node.getChildren();
		for (Node n : nodes) {
			if (n.getFile() != null) {
				fileNodeMap.remove(n.getFile());
			}
			if (n.getConfType().equals(ConfType.ACTION)) {
				uriNodeMap.remove(n.getPathInfo());
			}
			if (n.getObject() instanceof Reloadable) {
				Reloadable u = (Reloadable) n.getObject();
				u.unload();
			}
			prepareReload(n);
		}
	}

	protected void afterReload(Node node, ConfType type) {
		List<Node> nodes = node.getChildren();
		for (Node n : nodes) {
			if (n.getConfType().equals(type) && n.getObject() instanceof Reloadable) {
				Reloadable u = (Reloadable) n.getObject();
				u.afterLoad();
			}
			afterReload(n, type);
		}
	}

	protected void init(Node node) {
		List<Node> nodes = node.getChildren();
		for (Node n : nodes) {
			if (n.getObject() instanceof Reloadable) {
				Reloadable u = (Reloadable) n.getObject();
				u.init();
			}
			init(n);
		}
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		loadApps();
	}

	protected void loadApps() throws JAXBException {
		String filePath = getFilePath(appResource);
		Apps as = (Apps) appsXmlLoader.getUnmarshal().unmarshal(new File(filePath));
		apps = new Node(appResource);// TODO
		apps.setObject(new AppsExt(as, apps));
		apps.setConfType(ConfType.APPS);
		apps.getChildren().clear();
		apps.setFile(filePath);
		apps.setId("apps");

		fileNodeMap.put(new File(filePath).getPath(), apps);

		List<App> list = as.getApp();

		if (list != null) {
			for (App app : list) {
				String appFilePath = getFilePath(app.getResource());
				loadApp(apps, appFilePath);
			}
		}
		init(apps);
		if (devModel && fileMonitor == null) {
			fileMonitor = new FileAlterationMonitor(1000);
			File mFile = new File(filePath).getParentFile();
			FileAlterationObserver observer = new FileAlterationObserver(mFile);
			observer.addListener(new FileAlterationListenerAdaptor() {
				@Override
				public void onFileChange(File file) {
					String path = file.getPath();
					if (fileNodeMap.containsKey(path)) {
						log.debug("reload:" + path);
						Node node = fileNodeMap.get(path);
						ConfType type = node.getConfType();
						try {
							// reload fileNodeMap
							// reload uriNodeMap

							if (node.getParent() != null) {
								node.getParent().getChildren().remove(node);
							}
							prepareReload(node);
							
							if (type.equals(ConfType.APPS)) {
								loadApps();
							} else if (type.equals(ConfType.APP)) {
								loadApp(node.getParent(), path);
							} else if (type.equals(ConfType.MODULE)) {
								loadModule(node.getParent(), path);
							} else if (type.equals(ConfType.ACTIONS)) {
								loadActions(node.getParent(), path);
								
							} else if (type.equals(ConfType.SERVICES)) {
								loadServices(node.getParent(), path);
							} else if (type.equals(ConfType.ENTITYS)) {
								loadEntities(node.getParent(), path);
							} else if (type.equals(ConfType.FORMS)) {
								loadForms(node.getParent(), path);
							}
							afterReload(node.getParent(), type);

						} catch (JAXBException e) {
							e.printStackTrace();
							throw new ConfigException(e);
						}
					}

				}
			});
			fileMonitor.addObserver(observer);
			try {
				fileMonitor.start();
			} catch (Exception e) {
				e.printStackTrace();
				throw new ConfigException(e);
			}
		}
	}

	protected void loadApp(Node parent, String appFilePath) throws JAXBException {

		File file = new File(appFilePath);

		jhunter.conf.app.vo.App app = (jhunter.conf.app.vo.App) appXmlLoader.getUnmarshal().unmarshal(file);
		if (app == null || app.getModuleFiles() == null || app.getModuleFiles().getModuleFile() == null) {
			return;
		}
		Node node = new Node(app.getId());
		node.setParent(parent);
		node.setObject(new AppExt(app, node)); // TODO
		node.setConfType(ConfType.APP);
		node.setId(app.getId());

		node.setFile(file.getPath());

		node.setUriPath(app.getBaseContext()); // URI

		fileNodeMap.put(file.getPath(), node);

		parent.getChildren().add(node);

		List<ModuleFile> mfs = app.getModuleFiles().getModuleFile();
		for (ModuleFile moduleFile : mfs) {
			String resource = moduleFile.getResource();
			String moduleFilePath = getFilePath(resource);
			loadModule(node, moduleFilePath);
		}
	}

	protected void loadModule(Node parent, String moduleFilePath) throws JAXBException {
		File file = new File(moduleFilePath);
		Module module = (Module) moduleXmlLoader.getUnmarshal().unmarshal(file);
		Node node = new Node(module.getId());
		node.setParent(parent);
		node.setObject(new ModuleExt(module, node));
		node.setConfType(ConfType.MODULE);
		node.setId(module.getId());
		parent.getChildren().add(node);

		node.setFile(file.getPath());

		node.setUriPath(module.getBaseContext());

		fileNodeMap.put(file.getPath(), node);

		if (module != null) {
			if (module.getActionsFile() != null) {
				loadActions(node, getFilePath(module.getActionsFile().getResource()));
			}
			if (module.getServicesFile() != null) {
				loadServices(node, getFilePath(module.getServicesFile().getResource()));
			}
			if (module.getEntitiesFile() != null) {
				loadEntities(node, getFilePath(module.getEntitiesFile().getResource()));
			}
			if (module.getFormsFile() != null) {
				loadForms(node, getFilePath(module.getFormsFile().getResource()));
			}
		}

	}

	protected void loadActions(Node parent, String actionsFilePath) throws JAXBException {
		File file = new File(actionsFilePath);
		Actions actions = (Actions) actionsXmlLoader.getUnmarshal().unmarshal(file);
		
		if (actions != null) {

			Node node = new Node(actionsFilePath); // file path id
			node.setParent(parent);
			node.setObject(new ActionsExt(actions, node)); // TODO
			node.setConfType(ConfType.ACTIONS);
			node.setFile(file.getPath());
			node.setId(actionsFilePath);
			fileNodeMap.put(file.getPath(), node);

			parent.getChildren().add(node);
			for (Action action : actions.getAction()) {
				Node actionNode = new Node(action.getName()); // action node id
																// is action
																// name
				actionNode.setParent(node);
				ActionExt ae = new ActionExt(action, actionNode);
				actionNode.setObject(ae); // TODO
				actionNode.setConfType(ConfType.ACTION);
				actionNode.setUriPath(action.getPath()); // URI
				actionNode.setId(action.getName());

				node.getChildren().add(actionNode);
				String templateFileName = file.getParent() + "/template";
				ae.setTemplateDirctoryPath(templateFileName);
				uriNodeMap.put(actionNode.getPathInfo(), actionNode);// cache
			}
		}
	}

	protected void loadServices(Node parent, String servicesFilePath) throws JAXBException {
		File file = new File(servicesFilePath);
		Services services = (Services) servicesXmlLoader.getUnmarshal().unmarshal(file);
		if (services != null) {
			Node node = new Node(servicesFilePath);
			node.setParent(parent);
			node.setObject(new ServicesExt(services, node)); // TODO
			node.setConfType(ConfType.SERVICES);
			node.setId(servicesFilePath);
			fileNodeMap.put(file.getPath(), node);
			node.setFile(file.getPath());
			parent.getChildren().add(node);
			for (Service service : services.getService()) {
				Node serviceNode = new Node(service.getId());
				serviceNode.setParent(node);
				serviceNode.setObject(service); // TODO
				serviceNode.setConfType(ConfType.SERVICE);
				serviceNode.setId(service.getId());
				node.getChildren().add(serviceNode);
			}
		}
	}

	protected void loadEntities(Node parent, String entitisFilePath) throws JAXBException {
		File file = new File(entitisFilePath);
		Entities entities = (Entities) entitiesXmlLoader.getUnmarshal().unmarshal(file);
		if (entities != null) {
			Node node = new Node(entitisFilePath);
			node.setParent(parent);
			node.setObject(new EntitiesExt(entities, node)); // TODO
			node.setConfType(ConfType.ENTITYS);
			node.setFile(file.getPath());
			node.setId(entitisFilePath);
			fileNodeMap.put(file.getPath(), node);
			parent.getChildren().add(node);
			for (Entity entity : entities.getEntity()) {
				Node entityNode = new Node(entity.getId());
				entityNode.setParent(node);
				entityNode.setObject(entity); // TODO
				entityNode.setConfType(ConfType.ENTITY);
				entityNode.setId(entity.getId());
				node.getChildren().add(entityNode);

			}
		}

	}

	protected void loadForms(Node parent, String formsFilePath) throws JAXBException {
		File file = new File(formsFilePath);
		Forms forms = (Forms) formsXmlLoader.getUnmarshal().unmarshal(file);
		if (forms != null) {
			Node node = new Node(formsFilePath);
			node.setParent(parent);
			node.setObject(new FormsExt(forms, node)); // TODO
			node.setConfType(ConfType.FORMS);
			node.setFile(file.getPath());
			fileNodeMap.put(file.getPath(), node);
			parent.getChildren().add(node);
			for (EasyForm form : forms.getEasyForm()) {
				Node formNode = new Node(form.getName());
				formNode.setParent(node);
				formNode.setObject(form); // TODO
				formNode.setConfType(ConfType.FORM);
				node.getChildren().add(formNode);
			}
		}
	}

	public String getAppResource() {
		return appResource;
	}

	public void setAppResource(String appResource) {
		this.appResource = appResource;
	}

	public Node getApps() {
		return apps;
	}

	public void setApps(Node apps) {
		this.apps = apps;
	}

	public AppsXmlLoader getAppsXmlLoader() {
		return appsXmlLoader;
	}

	public void setAppsXmlLoader(AppsXmlLoader appsXmlLoader) {
		this.appsXmlLoader = appsXmlLoader;
	}

	public AppXmlLoader getAppXmlLoader() {
		return appXmlLoader;
	}

	public void setAppXmlLoader(AppXmlLoader appXmlLoader) {
		this.appXmlLoader = appXmlLoader;
	}

	public ModuleXmlLoader getModuleXmlLoader() {
		return moduleXmlLoader;
	}

	public void setModuleXmlLoader(ModuleXmlLoader moduleXmlLoader) {
		this.moduleXmlLoader = moduleXmlLoader;
	}

	public ActionsXmlLoader getActionsXmlLoader() {
		return actionsXmlLoader;
	}

	public void setActionsXmlLoader(ActionsXmlLoader actionsXmlLoader) {
		this.actionsXmlLoader = actionsXmlLoader;
	}

	public ServicesXmlLoader getServicesXmlLoader() {
		return servicesXmlLoader;
	}

	public void setServicesXmlLoader(ServicesXmlLoader servicesXmlLoader) {
		this.servicesXmlLoader = servicesXmlLoader;
	}

	public EntitiesXmlLoader getEntitiesXmlLoader() {
		return entitiesXmlLoader;
	}

	public void setEntitiesXmlLoader(EntitiesXmlLoader entitiesXmlLoader) {
		this.entitiesXmlLoader = entitiesXmlLoader;
	}

	public FormsXmlLoader getFormsXmlLoader() {
		return formsXmlLoader;
	}

	public void setFormsXmlLoader(FormsXmlLoader formsXmlLoader) {
		this.formsXmlLoader = formsXmlLoader;
	}

	public boolean isDevModel() {
		return devModel;
	}

	public void setDevModel(boolean devModel) {
		this.devModel = devModel;
	}

	public FileAlterationMonitor getFileMonitor() {
		return fileMonitor;
	}

	public void setFileMonitor(FileAlterationMonitor fileMonitor) {
		this.fileMonitor = fileMonitor;
	}

	public Map<PathInfo, Node> getUriNodeMap() {
		return uriNodeMap;
	}

	public void setUriNodeMap(Map<PathInfo, Node> uriNodeMap) {
		this.uriNodeMap = uriNodeMap;
	}

}
